在go的protobuf中进行自定义json tag标记及使用
在使用grpc-gateway的时候,测试时发现HTTP接口返回给前端的json数据的字段格式很不统一,所以需要标准化protobuf->json的映射关系
原因
- proro的字段命名很不规范,有全小写的,有大驼峰/小驼峰/下划线等等
- 使用了默认的 protoc-gen-go 插件,生成的json tag会尝试小驼峰以及omitempty,但如果是纯小写或大驼峰,则不会改变
解决方法
不使用 protoc-gen-go
比如使用 gogo,就可以完全自定义json tag的命名
参考如下例子
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
// Result example:
// type Post struct {
// Number int64 `protobuf:"bytes,1,opt,name=number,json=no1,proto3" json:"no2"`
// }
message Post {
int64 number = 1 [json_name="no1", (gogoproto.jsontag) = "no2"];
}
json_name + jsonpb
上个方案中已经出现了json_name,官方的说明如下,具体见参考链接
Generates JSON objects. Message field names are mapped to lowerCamelCase and become JSON object keys.
If the json_name field option is specified, the specified value will be used as the key instead.
Parsers accept both the lowerCamelCase name (or the one specified by the json_name option) and the original proto field name.
null is an accepted value for all field types and treated as the default value of the corresponding field type.
json_name并不会影响 protoc-gen-go 生成的go结构体中的json tag,而是会在 protobuf tag 中生成指定的json name
它的用途是在protobuf->json时被应用,而要让它起作用,encoding/json
包是不行的,它只认json tag。要使用 github.com/golang/protobuf/jsonpb
看如下例子理解
定义proto文件,强行在Blog中塞进了不同的字段命名格式
syntax = "proto3";
package blog;
option go_package = "./;blog";
service BlogService {
rpc Get (GetRequest) returns (GetResponse) {}
}
message Blog {
int64 id = 1 [json_name = "myid"];
string titleName = 2;
string author_name = 3;
string img = 4;
int64 CountNum = 5;
}
message GetRequest {
int64 id = 1;
}
message GetResponse {
Blog data = 1;
}
执行命令 protoc --go_out=paths=source_relative:. jsontag.proto
,生成pb文件中的 message Blog 对应的 Blog 结构体如下
- json tag是和message定义完全一致的
- 如果字段原本就是驼峰格式,那么默认情况下,protobuf中是不会额外出现
json=xxx
内容的- 见字段 img / titleName / CountNum
- 如果字段不是驼峰格式,或者指定了 json_name,那么protobuf中会出现
json=xxx
内容- 见字段 id / author_name
type Blog struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id int64 `protobuf:"varint,1,opt,name=id,json=myid,proto3" json:"id,omitempty"`
TitleName string `protobuf:"bytes,2,opt,name=titleName,proto3" json:"titleName,omitempty"`
AuthorName string `protobuf:"bytes,3,opt,name=author_name,json=authorName,proto3" json:"author_name,omitempty"`
Img string `protobuf:"bytes,4,opt,name=img,proto3" json:"img,omitempty"`
CountNum int64 `protobuf:"varint,5,opt,name=CountNum,proto3" json:"CountNum,omitempty"`
}
json序列化
import "github.com/golang/protobuf/jsonpb" func main() { b := blog.Blog{Id: 42, TitleName: "nothing", AuthorName: "who"} m := jsonpb.Marshaler{ OrigName: false, EnumsAsInts: false, EmitDefaults: false, Indent: "", AnyResolver: nil, } fmt.Println(m.MarshalToString(&b)) // {"myid":"42","titleName":"nothing","authorName":"who"} <nil> // 使用原始的 protobuf 字段名 m.OrigName = true fmt.Println(m.MarshalToString(&b)) // {"id":"42","titleName":"nothing","author_name":"who"} <nil> // 零值字段输出 m.EmitDefaults = true fmt.Println(m.MarshalToString(&b)) // {"id":"42","titleName":"nothing","author_name":"who","img":"","CountNum":"0"} <nil> // 零值字段输出,但使用 protobuf 的 json tag m.OrigName = false fmt.Println(m.MarshalToString(&b)) // {"myid":"42","titleName":"nothing","authorName":"who","img":"","CountNum":"0"} <nil> // 自定义缩进字符 m.Indent = "|——" fmt.Println(m.MarshalToString(&b)) // { // |——"id": "42", // |——"titleName": "nothing", // |——"author_name": "who", // |——"img": "", // |——"CountNum": "0" // } <nil> // 直接输出字符串 fmt.Println(b.String()) // id:42 titleName:"nothing" author_name:"who" }
参考链接
原文链接
Defining custom go struct tags for protobuf message fields
Language Guide (proto3)